﻿using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace AIthin.Neuro
{

    [Serializable]
    public class Neuron : INeuron
    {
        public float X { get; internal set; }
        public float Y { get; internal set; }
        public float Z { get; internal set; }
        /// <summary>
        /// ident number
        /// </summary>
        public int ID { get; internal set; }
        public int LifeTime { get; protected set; }
        public bool Prozessed { get; protected set; }
        public bool Activated { get; protected set; }
        /// <summary>
        /// Neurons list for all neurons (static)
        /// </summary>
        public static List<int> Neurons = new List<int>();
        /// <summary>
        /// Neuron's inputs count.
        /// </summary>
        public int InputsCount { get; protected set; }
        /// <summary>
        /// Neuron's inputs count.
        /// </summary>
        int OutputsCount { get; set; }
        /// <summary>
        /// gesamt berechnete input signal
        /// </summary>
        float inputSignal { get; set; }
        /// <summary>
        /// gesamt berechnete output signal
        /// </summary>
        float outputSignal { get; set; }
        bool AllInputsActivated { get; set; }
        const short maxDurationBeActiv = 3;
        const int InputMultiplikator = 5;
        int DurationBeActiv { get; set; }

        public Dendrit[] inputs;
        public Axon[] outputs;

        float treshold()
        {
            return 2f / InputsCount;
        }
        public Neuron(int Ident, int inputsCount, int outputsCount)
        {
            ID = Ident;

            Neurons.Add(ID);

            Prozessed = false;
            Activated = false;
            AllInputsActivated = false;
            DurationBeActiv = maxDurationBeActiv;
            LifeTime = 0;
            InputsCount = inputsCount;
            OutputsCount = outputsCount;

            inputs = new Dendrit[inputsCount];
            outputs = new Axon[outputsCount];
            for (int i = 0; i < inputsCount; i++) inputs[i] = new Dendrit();
            for (int i = 0; i < outputsCount; i++) outputs[i] = new Axon();
        }
        public Neuron(int Ident, int inputsCount, int outputsCount, float x, float y, float z) :
            this(Ident, inputsCount, outputsCount)
        {
            X = x;
            Y = y;
            Z = z;
        }
        /// <summary>
        /// returns connected neurons numbers or IDs
        /// </summary>
        /// <param name="inputsOut">input neurons IDs</param>
        /// <param name="outputsOut">outputs neurons IDs</param>
        public void getConnections(out int[] inputsOut, out int[] outputsOut)
        {
            inputsOut = new int[InputsCount];
            outputsOut = new int[OutputsCount];
            int i = 0;
            int j = 0;
            foreach (Dendrit d in inputs) inputsOut[i++] = d.ConnectedNeuron;
            foreach (Axon a in outputs) outputsOut[j++] = a.ConnectedNeuron;
        }

        public void getInputs(out int[] inputsOut, out float[] weightsOut)
        {
            inputsOut = new int[InputsCount];
            weightsOut = new float[InputsCount];

            int i = 0;
            foreach (Dendrit d in inputs)
            {
                inputsOut[i] = d.ConnectedNeuron;
                weightsOut[i++] = d.Weight;
            }

        }

        public bool ConnectToOutputOf(ref Neuron NeuronToConnect)
        {
            // check inputscount
            // check if that neuron is already connected
            foreach (Dendrit d in inputs)
            {
                if (d.ConnectedNeuron == NeuronToConnect.ID)
                {
                    Debug.WriteLine("neuron " + this.ID.ToString() + " double connection to: " + NeuronToConnect.ID.ToString());
                    return false;
                }
            }

            for (int i = 0; i < InputsCount; i++)
            {
                // find free input
                if (inputs[i].ConnectedNeuron == 0)
                {
                    for (int j = 0; j < NeuronToConnect.OutputsCount; j++)
                    {
                        //find free output
                        if (NeuronToConnect.outputs[j].ConnectedNeuron == 0)
                        {

                            NeuronToConnect.outputs[j].ConnectedNeuron = this.ID;
                            NeuronToConnect.outputs[j].ConnectedDendrit = i;
                            inputs[i].ConnectedNeuron = NeuronToConnect.ID;
                            inputs[i].ConnectedAxon = j;
                            //
                            //Debug.WriteLine("neuron " + this.ID.ToString() + " dendrit " + i.ToString()
                            //    + " to: " + NeuronToConnect.ID.ToString() + " axon " + j.ToString());
                            return true;
                        }
                    }
                    // no free outputs
                    NeuronToConnect.AddOutput();
                    if (!ConnectToOutputOf(ref NeuronToConnect))
                    {
                        Debug.WriteLine(this.ID, "!!! no free axons for");
                        return false;
                    }
                    else
                        return true;
                }
            }
            this.AddInput();
            if (!ConnectToOutputOf(ref NeuronToConnect))
            {
                Debug.WriteLine(this.ID, "!!! no free dendrits for");
                return false;
            }
            else
                return true;
        }
        public bool AddOutput()
        {
            OutputsCount++;
            //RES to slow
            Axon[] newOutputs = new Axon[OutputsCount];
            outputs.CopyTo(newOutputs, 0);
            outputs = newOutputs;
            outputs[OutputsCount - 1] = new Axon();
 //           Debug.WriteLine(this.ID, "output added");
            return true;
        }

        public bool AddInput()
        {
            InputsCount++;
            //RES to slow
            Dendrit[] newInputs = new Dendrit[InputsCount];
            inputs.CopyTo(newInputs, 0);
            inputs = newInputs;
            inputs[InputsCount - 1] = new Dendrit();
//            Debug.WriteLine(this.ID, "input added");
            return true;
        }

        public bool Computate()
        {

            //TODO when return, output signal will be twise computed, resourses
            if (Prozessed)
            {
 //               Debug.WriteLine(this.ID, "this neuron was processed");
                return true;
            }
            //TODO Check for Ticks and unset Prozessed for a dendrites
            if (!AllInputsActivated)
            {
 //               Debug.WriteLine(this.ID, "not all inputs active for neuron ");
                return false;
            }

            //activate inertion
            if (DurationBeActiv > 0)
            {
                Activated = true;
                DurationBeActiv--;
            }
            else
            {
                Activated = false;
            }

            // when neuron is activated don't need it compute

            inputSignal = 0f;
            outputSignal = 0f;
            // sigmoid function
            foreach (Dendrit dendrit in inputs) inputSignal += dendrit.Out();
            if (inputSignal > treshold())
            {
                Activated = true;
                DurationBeActiv = maxDurationBeActiv;
            }
            LifeTime += 1;
            // set outputs
            if (Activated)
            {
                outputSignal = 1.0f;
                foreach (Axon axon in outputs) axon.Signal = outputSignal;
            }
            // neuron is activated
            Prozessed = true;

            return true;
        }
        /// <summary>
        /// increase weight of active input (learn)
        /// </summary>
        /// <param name="inputNeuron">connected input neuron</param>
        /// <param name="weightDelta">weight (delta)</param>
        /// <returns>true - increased, false - input neuron not found</returns>
        public bool strengthInputs(int inputNeuron, float weightDelta)
        {
            // normalise weights
            float normWeight = -0.01f * weightDelta / inputs.Length;
            foreach (Dendrit dendrit in inputs)
            {
                if (inputNeuron == dendrit.ConnectedNeuron)
                    dendrit.weightGrow(weightDelta);
                else
                    dendrit.weightGrow(normWeight);

            }
            return true;
        }
        /// <summary>
        /// activate input and transfer signal to processing
        /// </summary>
        /// <param name="input">number(index) of input of this neuron</param>
        /// <param name="signal">input signal value</param>
        public void ActivateInput(int input, float signal)
        {
            inputs[input].Prozessed = true;
            inputs[input].Signal = signal;
            foreach (Dendrit dendrit in inputs) if (!dendrit.Prozessed) return;
            AllInputsActivated = true;
        }
        /// <summary>
        /// Set inputsignal for neuron 
        /// (used for retina neurons)
        /// </summary>
        /// <param name="inputSignal">value 1-black, 0-white</param>
        public void SetInput(short inputSignal)
        {
            //TODO Make input as float
            foreach (Dendrit d in inputs)
            {
                d.Weight = 1.0f;
                d.Signal = Convert.ToSingle(InputMultiplikator*inputSignal);
                d.Prozessed = true;
                d.ConnectedNeuron = 0;
            }
            this.AllInputsActivated = true;
        }
        /// <summary>
        /// set or analyse output signal for last(end) neurons
        /// </summary>
        /// <param name="outputSignal">value of output 1-256</param>
        public void SetOutput(short outputSignal)
        {
            this.Prozessed = true;
            this.DurationBeActiv = maxDurationBeActiv;
        }
        /// <summary>
        /// neuron must be reset after wave or tick for network was computed
        /// </summary>
        public void Reset()
        {
            this.Prozessed = false;
            this.AllInputsActivated = false;
            Activated = false;
            // inputs reset
            foreach (Dendrit d in inputs) d.Reset();
            //outputs reset
            foreach (Axon a in outputs) a.Reset();
            //Debug.WriteLine(this.ID, "reset neuron for next tick");
        }
        public bool inRange(float inX, float inY, float inZ, float Range)
        {
            if (Math.Abs(inZ - Z) < 0.5f) return false;
            if (Math.Sqrt((inX - X) * (inX - X) + (inY - Y) * (inY - Y) + (inZ - Z) * (inZ - Z)) < Range)
                return true;
            else
                return false;
        }


    }
}
